/*
 *  Floor.m
 *  pogostick
 *
 * The floor is the thing in the level that the pogostick jumps on
 * most of the time. It has "power up" pills floating above it.
 * Also the height of the lowest position in the lowest floor is
 * used to determine if the pogostick has "fallen off of the earth",
 * and so needs to die.
 *
 *  Created by chuck@kollar.com on 10/13/09.
 *  Copyright 2009 Charles P. Kollar All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#import "Floor.h"

// set in menu.m
extern int soundp, musicp;


@implementation Floor

@synthesize identifier;
@synthesize space;
@synthesize staticBody;
@synthesize collisionType;
@synthesize pillToRemove;
@synthesize pp;


// This method should be called right before the call to 'cpSpaceStep(space, dt);' in the
// time slice method '-(void) step: (ccTime) delta' called by the scheduler.
// The function will remove the pills, credit the player, and play a sound effect...
-(void) processQueuedStickHitPillEventsWithEmitter:(ParticleSystem *)emitter {
	NSInteger cnt = [pillToRemove count];
	if (cnt == 0) return;
	for (int i = cnt-1; i >= 0; --i) {
		ShapeWrapper *item = [[pillToRemove objectAtIndex: (unsigned)i] retain];
		// cpShape is not an NSObject it is a C struct, so we can't save them in an ObjectiveC mutable array...
        
		//NSLog(@"removing a shape... body %x shape %x\n", pill->body, pill);
        
        cpContact *con = [item contacts];
        [emitter setPosition: ccp(con->p.x, con->p.y)];
        [emitter setTotalParticles: 3.0f];
        
        [pillToRemove removeObject: item];
        [item cpFree];
        [item release]; // this does a free but not instantly
        }
	
	// http://www.a1freesoundeffects.com/button.html
    if (soundp != 0) {
        [[SimpleAudioEngine sharedEngine] playEffect: STICK_HIT_PILL_WAV];
    }
}


// These define a box in which a PUP can be placed as the floor is created
#define PUP_AREA_WIDTH              150
#define PIP_AREA_HEIGHT             600

extern id amulet;

// Randomly distribute power up pills in the space attached to the staticBody through the area
// above the terrain.
-(void)afixPillsInArea: (cpVect)a B:(cpVect)b {
    //NSLog(@"LineSegment for PUP a = <%f,%f> b = <%f,%f>", a.x, a.y, b.x, b.y);
    // the number of areas in this line segment...
    int areas = (b.x-a.x)/PUP_AREA_WIDTH;
    // whether the line is sloping up or down, put the PUP above the line...
    cpFloat maxY = (a.y > b.y) ? a.y : b.y;
    
    // put a PUP in every area...
	for(int i=0; i<areas; i++){
        // the location of the center if the pill is randomly placed in the area.
        cpFloat pillX = a.x+(i*PUP_AREA_WIDTH)+(rand()%PUP_AREA_WIDTH);
        cpFloat pillY = maxY+(FLOOR_SEGMENT_RADIUS/2)+(rand()%PIP_AREA_HEIGHT);
        cpVect pillVect = cpv(pillX, pillY);
        //NSLog(@"PUP <x,y> = <%f,%f>", pillX, pillY);
        
        // Here we place the amulet.
        // There is a 1 in 100 chance that it will be placed where a pill would have been.
        // So it should just fall onto the floor since it's not attached to a static body.
        if ((amulet == nil) && (rand()%100 == 1)) {
            amulet = [[Bug alloc] initInSpace: space group:5 position: pillVect size: cpv(40.0, 40.0)];
            NSLog(@"Amulet Placed!");
            return;
        }
        
        cpFloat radius = (rand() % 15) + 10.0f;
        cpBody *pillBody = cpBodyNew(1.0f, cpMomentForCircle(1.0f, 0.0f, radius, cpvzero));
        //cpBody *pillBody = cpBodyNew(INFINITY,INFINITY);
        pillBody->p = pillVect; 
        pillBody->m = 0.0f;
        // it's OK to leave the static body out of the space is that any object colliding with it won't
        // be able to push it because of its infinite mass. Also, because it never moves, it doesn't need
        // it's position or velocity updated. It will end up accelerating due to gravity.
        // However, it will be leaked since we are using 'cpSpaceFreeChildren(space);' to GC things
        // in the space. So we add it...
        //cpSpaceAddBody(space, pillBody);
        cpShape *shape = cpCircleShapeNew(pillBody, radius, cpvzero);
        shape->collision_type = kColl_Pill;
        // We need to set this to 'nil' so that the function 'removeShapeWrapperForShapeInPillCage'
        // called in the cp physics callback 'bulletHitGroundEvent' found in GameLayer.m doesn't try
        // to delete this shape from a 'PillCage'.
        shape-> data = nil;
        cpSpaceAddStaticShape(space, shape);
        // since gravity will not have an effect on the body, we don't need a constraint to hold it in place...
        //cpConstraint *fastener = cpPinJointNew(staticBody, dropletBody, center, cpvzero);
        //cpSpaceAddConstraint(space, fastener);
        //NSLog(@"pill[%d] at %f, %f\n", i, pillBody->p.x, pillBody->p.y);
	}
}

cpGroup floorGroup;
cpFloat spaceHeight;


-(id)initInSpace: (cpSpace *)sp toStaticBody:(cpBody *)sb identifier:(NSString *)ident collisionType:(cpCollisionType)ct group:(cpGroup)gr spaceHeight:(cpFloat)sh data:(NSString *)dat
{
    if (self = [super init]) {
        identifier = [ident retain];
        space = sp;
        staticBody = sb;
        collisionType = ct;
        pillToRemove = [[NSMutableArray alloc] initWithCapacity: 10];
        
        // These we only really need to build the structure...
        floorGroup = gr;
        spaceHeight = sh;
        
        // parse the data field of the svg path using the PathParser interface setup below...
        pp = [[PathParser alloc] initWithPathHandler:self data:dat];
        @try {
            [pp doParse];
        }
        @catch (NSException *e) {
            NSLog(@"Problem parsing Floor id = '%s', Caught Exception:%@; Reason:%@", ident, [e name], [e reason]);
            [identifier release];
            [pillToRemove release];
            return nil;
        }
        @finally {
            // Remember that it ALWAYS goes through finally, even if there was nothing wrong...
            //[pp release];
        }
    }
    return self;
}

-(void) dealloc
{
    // staticBody and space are released by level...
    [identifier release];
    [pillToRemove release];
    // This causes a signal EXC_BAD_ACCESS yet instrumentation shows pp to being leaked?!
    //[pp release];
    
	[super dealloc];
}



/**
 **
 ** PathParser methods that need to be implemented according to the PathParser
 ** protocol defined in PathParser.h
 **
 **/

// This lowestY code is used to tell when the pogostick falls past any
// possible floor that it could be in, and so it will just continue falling
// and falling... Not a very interesting situation. In this case the pogostick
// is essentially "dead".
cpFloat lowestY = 0.0f;

// The current point is used to hold the last point that was processed
// The parser is always acting on the "next point" (which after it is
// processed becomes the currentPoint.
cpVect currentPoint;

void setCurrentPoint(cpVect p) {
    currentPoint = p;
    if (p.y < lowestY) lowestY = p.y;
}
void setLowestFloorY(cpFloat y) {
    lowestY = y;
}
cpFloat getLowestFloorY() {
    return lowestY;
}


/**
 * Invoked when the path starts.
 * @exception ParseException if an error occured while processing the path
 */
-(void) startPath {} // throws ParseException ;

/**
 * Invoked when the path ends.
 * @exception ParseException if an error occured while processing the path
 */
-(void) endPath {}; // throws ParseException ;

/**
 * Invoked when a relative moveto command has been parsed.
 * <p>Command : <b>m</b>
 * @param x the relative x coordinate for the end point
 * @param y the relative y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) movetoRelX:(float)x Y:(float)y {
    setCurrentPoint(cpvadd(currentPoint,cpv(x,-y)));
}

/**
 * Invoked when an absolute moveto command has been parsed.
 * <p>Command : <b>M</b>
 * @param x the absolute x coordinate for the end point
 * @param y the absolute y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) movetoAbsX:(float)x Y:(float)invertedY {
    float y = spaceHeight - invertedY;
    setCurrentPoint(cpv(x,y));
}

/**
 * Invoked when a closepath has been parsed.
 * <p>Command : <b>z</b> | <b>Z</b>
 * @exception ParseException if an error occured while processing the path
 */
-(void) closePath {}; // throws ParseException;

/*
 * This routine will create a Chipmunk Physics line segment shape between the
 * 'currentPoint' and the 'ToVect' (b) point.
 *
 * There are some potential problems here given the way that the floors are created
 * (e.g. with Inkscape a drawing package). The problem is that it is possible for
 * lots of shapes to be created (that don't really add value) but do require processing
 * for collisions (for example).
 *
 * It also makes a greate deal of sense to put "power up pills (PUPs)" in the space at this
 * point. The current algorithm can flood the space with pills which also causes
 * the program to run like sludge. A reasonable algorithm would be to put PUPs
 * somewhere within areas along the floor line. The first area would start at the
 * first point and subsequent areas continued from there.
 */
-(void)lineToVect:(cpVect)b {
    cpShape *seg = cpSegmentShapeNew(staticBody, currentPoint, b, FLOOR_SEGMENT_RADIUS);
    seg->u = 1.0f;
    
    // The collisionType is used by the Chipmunk Physics 'cpSpaceAddCollisionPairFunc'
    // to mark a call back function for collisions with this floor.
    seg->collision_type = collisionType;
    
    // In Chupmunk, things that are in the same group don't run into one another.
    seg->group = floorGroup;
    
    cpSpaceAddStaticShape(space, seg);
    
    [self afixPillsInArea:currentPoint B:b];
    
    setCurrentPoint(b);
}


/**
 * Invoked when a relative line command has been parsed.
 * <p>Command : <b>l</b>
 * @param x the relative x coordinates for the end point
 * @param y the relative y coordinates for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) linetoRelX:(float)x Y:(float)y {
    cpVect b = cpvadd(currentPoint,cpv(x,-y));
    [self lineToVect:b];
}

/**
 * Invoked when an absolute line command has been parsed.
 * <p>Command : <b>L</b>
 * @param x the absolute x coordinate for the end point
 * @param y the absolute y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) linetoAbsX:(float)x Y:(float)y {
    cpVect b = cpv(x,spaceHeight - y);
    [self lineToVect:b];
}

/**
 * Invoked when an horizontal relative line command has been parsed.
 * <p>Command : <b>h</b>
 * @param x the relative X coordinate of the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) linetoHorizontalRelX:(float)x {
    cpVect b = cpvadd(currentPoint,cpv(x,0.0f));
    [self lineToVect:b];
}

/**
 * Invoked when an horizontal absolute line command has been parsed.
 * <p>Command : <b>H</b>
 * @param x the absolute X coordinate of the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) linetoHorizontalAbsX:(float)x {
    cpVect b = cpv(x,0.0f);
    [self lineToVect:b];
}

/**
 * Invoked when a vertical relative line command has been parsed.
 * <p>Command : <b>v</b>
 * @param y the relative Y coordinate of the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) linetoVerticalRelY:(float)y {
    cpVect b = cpvadd(currentPoint,cpv(0.0f,-y));
    [self lineToVect:b];
}

/**
 * Invoked when a vertical absolute line command has been parsed.
 * <p>Command : <b>V</b>
 * @param y the absolute Y coordinate of the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) linetoVerticalAbsY:(float)y {
    cpVect b = cpv(0.0f,spaceHeight - y);
    [self lineToVect:b];
}

// lerp, bezier and most of cubicBezier taken from...
// http://www.cubic.org/docs/bezier.htm
// it was written by Nils Pipenbrinck!!

// simple linear interpolation between two points
void lerp (cpVect *dest, cpVect a, cpVect b, cpFloat t) {
    (*dest).x = a.x + (b.x-a.x)*t;
    (*dest).y = a.y + (b.y-a.y)*t;
}

// evaluate a point on a bezier-curve. t goes from 0 to 1.0
void cubicBezier (cpVect *dest, cpFloat t, cpVect a, cpVect b, cpVect c, cpVect d) {
    cpVect ab,bc,cd,abbc,bccd;
    lerp (&ab, a,b,t);           // point between a and b (green)
    lerp (&bc, b,c,t);           // point between b and c (green)
    lerp (&cd, c,d,t);           // point between c and d (green)
    lerp (&abbc, ab,bc,t);       // point between ab and bc (blue)
    lerp (&bccd, bc,cd,t);       // point between bc and cd (blue)
    lerp (dest, abbc,bccd,t);   // point on the bezier-curve (black)
}

-(void) drawRelCubicBezierShapesWithPointsOnCurve:(int)pointsOnCurve X1:(cpFloat)x1 Y1:(cpFloat)y1 X2:(cpFloat)x2 Y2:(cpFloat)y2 X:(cpFloat)x Y:(cpFloat)y {
    cpVect a = currentPoint; // is the starting point
    cpVect b = cpvadd(currentPoint, cpv(x1,-y1)); // control point 1
    cpVect c = cpvadd(currentPoint, cpv(x2,-y2)); // control point 2
    cpVect d = cpvadd(currentPoint, cpv(x,-y)); // is the ending point
    cpVect last = currentPoint;
    cpVect next;
    // so There are 'pointsOnCurve' in addition to the starting point
    // a or currentPoint and the ending point <x,y>
    for (int i=0; i<pointsOnCurve; i++) {
        cpFloat t = (cpFloat)(i+1)/(cpFloat)(pointsOnCurve+2);
        // compute the next point on the curve...
        cubicBezier(&next, t, a, b, c, d);
        // draw the next point on the curve...
        cpShape *seg = cpSegmentShapeNew(staticBody, last, next, FLOOR_SEGMENT_RADIUS);
        seg->u = 1.0f;
        seg->collision_type = collisionType;
        seg->group = floorGroup;
        cpSpaceAddStaticShape(space, seg);
        last = next;
    }
    // this last segment is drawn from the last point on the curve to the ending point.
    setCurrentPoint(last);
    //[self linetoAbsX:d.x Y:d.y];
}

/**
 * Invoked when a relative cubic bezier curve command has been parsed.
 * <p>Command : <b>c</b>
 * @param x1 the relative x coordinate for the first control point
 * @param y1 the relative y coordinate for the first control point
 * @param x2 the relative x coordinate for the second control point
 * @param y2 the relative y coordinate for the second control point
 * @param x the relative x coordinate for the end point
 * @param y the relative y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoCubicRelX1:(float)x1 Y1:(float)y1 X2:(float)x2 Y2:(float)y2 X:(float)x Y:(float)y {
    //[self drawRelCubicBezierShapesWithPointsOnCurve:5 X1:x1 Y1:y1 X2:x2 Y2:y2 X:x Y:y];
    // NEED TOFIND A BETTER APPROXIMATION OF A CUBIC BEZIER THAN THIS!!!
    [self linetoRelX:x Y:y];
}

-(void) drawAbsCubicBezierShapesWithPointsOnCurve:(int)pointsOnCurve X1:(cpFloat)x1 Y1:(cpFloat)y1 X2:(cpFloat)x2 Y2:(cpFloat)y2 X:(cpFloat)x Y:(cpFloat)y {
    cpVect a = currentPoint; // is the starting point
    cpVect b = cpv(x1, spaceHeight - y1); // control point 1
    cpVect c = cpv(x2, spaceHeight - y2); // control point 2
    cpVect d = cpv(x, spaceHeight - y); // is the ending point
    cpVect last = currentPoint;
    cpVect next;
    // so There are 'pointsOnCurve' in addition to the starting point
    // a or currentPoint and the ending point <x,y>
    for (int i=0; i<pointsOnCurve; i++) {
        cpFloat t = (cpFloat)(i+1)/(cpFloat)(pointsOnCurve+2);
        // compute the next point on the curve...
        cubicBezier(&next, t, a, b, c, d);
        // draw the next point on the curve...
        cpShape *seg = cpSegmentShapeNew(staticBody, last, next, FLOOR_SEGMENT_RADIUS);
        seg->u = 1.0f;
        seg->collision_type = collisionType;
        seg->group = floorGroup;
        cpSpaceAddStaticShape(space, seg);
        last = next;
    }
    // this last segment is drawn from the last point on the curve to the ending point.
    setCurrentPoint(last);
    [self linetoAbsX:x Y:y];
}

/**
 * Invoked when an absolute cubic bezier curve command has been parsed.
 * <p>Command : <b>C</b>
 * @param x1 the absolute x coordinate for the first control point
 * @param y1 the absolute y coordinate for the first control point
 * @param x2 the absolute x coordinate for the second control point
 * @param y2 the absolute y coordinate for the second control point
 * @param x the absolute x coordinate for the end point
 * @param y the absolute y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoCubicAbsX1:(float)x1 Y1:(float)y1 X2:(float)x2 Y2:(float)y2 X:(float)x Y:(float)y {
    [self drawAbsCubicBezierShapesWithPointsOnCurve:5 X1:x1 Y1:y1 X2:x2 Y2:y2 X:x Y:y]; 
    // NEED TOFIND A BETTER APPROXIMATION OF A CUBIC BEZIER THAN THIS!!!
    //[self linetoAbsX:x Y:y];
}

/**
 * Invoked when a relative smooth cubic bezier curve command has
 * been parsed. The first control point is assumed to be the
 * reflection of the second control point on the previous command
 * relative to the current point.
 * <p>Command : <b>s</b>
 * @param x2 the relative x coordinate for the second control point
 * @param y2 the relative y coordinate for the second control point
 * @param x the relative x coordinate for the end point
 * @param y the relative y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoCubicSmoothRelX2:(float)x2 Y2:(float)y2 X:(float)x Y:(float) y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoRelX:x Y:y];
}

/**
 * Invoked when an absolute smooth cubic bezier curve command has
 * been parsed. The first control point is assumed to be the
 * reflection of the second control point on the previous command
 * relative to the current point.
 * <p>Command : <b>S</b>
 * @param x2 the absolute x coordinate for the second control point
 * @param y2 the absolute y coordinate for the second control point
 * @param x the absolute x coordinate for the end point
 * @param y the absolute y coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoCubicSmoothAbsX2:(float)x2 Y2:(float)y2 X:(float)x Y:(float)y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoAbsX:x Y:y];
}

/**
 * Invoked when a relative quadratic bezier curve command has been parsed.
 * <p>Command : <b>q</b>
 * @param x1 the relative x coordinate for the control point
 * @param y1 the relative y coordinate for the control point
 * @param x the relative x coordinate for the end point
 * @param y the relative x coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoQuadraticRelX1:(float)x1 Y1:(float)y1 X:(float)x Y:(float)y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoRelX:x Y:y];
}

/**
 * Invoked when an absolute quadratic bezier curve command has been parsed.
 * <p>Command : <b>Q</b>
 * @param x1 the absolute x coordinate for the control point
 * @param y1 the absolute y coordinate for the control point
 * @param x the absolute x coordinate for the end point
 * @param y the absolute x coordinate for the end point
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoQuadraticAbsX1:(float)x1 Y1:(float)y1 X:(float)x Y:(float) y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoAbsX:x Y:y];
}

/**
 * Invoked when a relative smooth quadratic bezier curve command
 * has been parsed. The control point is assumed to be the
 * reflection of the control point on the previous command
 * relative to the current point.
 * <p>Command : <b>t</b>
 * @param x the relative x coordinate for the end point 
 * @param y the relative y coordinate for the end point 
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoQuadraticSmoothRelX:(float)x Y:(float)y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoRelX:x Y:y];
}

/**
 * Invoked when an absolute smooth quadratic bezier curve command
 * has been parsed. The control point is assumed to be the
 * reflection of the control point on the previous command
 * relative to the current point.
 * <p>Command : <b>T</b>
 * @param x the absolute x coordinate for the end point 
 * @param y the absolute y coordinate for the end point 
 * @exception ParseException if an error occured while processing the path
 */
-(void) curvetoQuadraticSmoothAbsX:(float)x Y:(float)y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoAbsX:x Y:y];
}

/**
 * Invoked when a relative elliptical arc command has been parsed. 
 * <p>Command : <b>a</b>
 * @param rx the X axis radius for the ellipse
 * @param ry the Y axis radius for the ellipse 
 * @param xAxisRotation the rotation angle in degrees for the ellipse's
 *                      X-axis relative to the X-axis
 * @param largeArcFlag the value of the large-arc-flag 
 * @param sweepFlag the value of the sweep-flag 
 * @param x the relative x coordinate for the end point 
 * @param y the relative y coordinate for the end point 
 * @exception ParseException if an error occured while processing the path
 */
-(void) arcRelRx:(float)rx Ry:(float)ry XAxisRotation:(float)xar LargeArcFlag:(BOOL)laf SweepFlag:(BOOL)sw X:(float)x Y:(float)y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoRelX:x Y:y];
}
/**
 * Invoked when an absolute elliptical arc command has been parsed.
 * <p>Command : <b>A</b>
 * @param rx the X axis radius for the ellipse
 * @param ry the Y axis radius for the ellipse 
 * @param xAxisRotation the rotation angle in degrees for the ellipse's
 *                      X-axis relative to the X-axis
 * @param largeArcFlag the value of the large-arc-flag 
 * @param sweepFlag the value of the sweep-flag 
 * @param x the absolute x coordinate for the end point 
 * @param y the absolute y coordinate for the end point 
 * @exception ParseException if an error occured while processing the path
 */
-(void) arcAbsRx:(float)rx Ry:(float)ry XAxisRotation:(float)xar LargeArcFlag:(BOOL)laf SweepFlag:(BOOL)sw X:(float)x Y:(float)y {
    // NEED TOFIND A BETTER APPROXIMATION THAN THIS!!!
    [self linetoAbsX:x Y:y];
}

@end
